#!/usr/bin/env python

"""
 Converts old (deprecated) syntax into new.

 It actually converts special calls into JooScript syntax, "var" into "let",
 and replacing namespaces.

 Author: Evgen Burzak <buzzilo@gmail.com>
"""

import sys
import node2js, joo_node2js, interpr, defs

NodeToJS = node2js.NodeToJS

# JooScript constants

const = dict(
    PUBLIC = 0x1,
    HIDDEN = 0x2,
    STATIC = 0x4,
    GLOBAL = 0x8,
    FINAL = 0x10,
    SEALED = 0x20,
    FROZEN = 0x40,
    # internal is a SEALED and FROZEN and HIDDEN
    INTERNAL = 0x20 | 0x40 | 0x2
)

class NodeToJoo(NodeToJS):

    def __init__(self): 
        NodeToJS.__init__(self)
        self.exec_ctx = interpr.PyJS()
        for c, v in const.copy().iteritems():
            self.exec_ctx.globals["__%s__" % c] = v

    restArg = "__rest__"

    def convert_namespace_flags(self, flags):
        value = self.exec_ctx.exec_(flags)
        ns = []
        if value & (const['INTERNAL']) == const['INTERNAL']: 
          ns.append("internal")
        else:
          if value & const['HIDDEN']: ns.append("hidden")
          if value & const['FROZEN']: ns.append("frozen")
          if value & const['SEALED']: ns.append("sealed")
        if value & const['PUBLIC']: ns.append("public")
        if value & const['STATIC']: ns.append("static")
        if value & const['GLOBAL']: ns.append("global")
        if value & const['FINAL']: ns.append("final")
        return ns

    def convert_unit(self, n, i, c):
        args = n[1]
        assert len(args) == 3
        assert args[2].type == "FUNCTION"
        path_ = self.o(args[0], i, c)
        file_ = self.o(args[1], i, c)
        fun = args[2]
        unit_obj = root_obj = glob_obj = ""

        c['unit'] = {}

        if len(fun.params) >= 1:
            unit_obj = c['unit']['unit_obj'] = fun.params[0]
            if fun.params[0] != joo_node2js.JooNodeToJS.unit_obj:
                defs.err("WARNING: unit object name is differ from " + joo_node2js.JooNodeToJS.unit_obj)

        if len(fun.params) >= 2:
            root_obj = c['unit']['root_obj'] = fun.params[1]
            if fun.params[1] != joo_node2js.JooNodeToJS.root_obj:
                defs.err("WARNING: root object name is differ from " + joo_node2js.JooNodeToJS.root_obj)
            root_obj = "," + root_obj

        if len(fun.params) == 3:
            glob_obj = c['unit']['glob_obj'] = fun.params[2]
            if fun.params[2] != joo_node2js.JooNodeToJS.glob_obj:
                defs.err("WARNING: glob object name is differ from " + joo_node2js.JooNodeToJS.glob_obj)
            glob_obj = "," + glob_obj

        #print path_, file_, unit_obj, root_obj, glob_obj
        return "$Unit(%s,%s,function(%s%s%s)%s)"% (path_, file_, unit_obj, root_obj, glob_obj,
                                    self.o(fun.body, i, c))

    def convert_package(self, n, i, c):
        context = n[0]
        if ("unit" in c and len(context) == 2 and \
                context[0].value in (c["unit"]["unit_obj"], "this")) \
                or context.type == "IDENTIFIER":
            assert n[1][1].type == "FUNCTION"
            fun = n[1][1]
            assert n[1][0].type == "STRING"
            group_ns = n[1][0].value
            c['package'] = {}
            group_obj = ""
            if len(fun.params)>0:
                group_obj = c['package']["group_obj"] = fun.params[0]
                if fun.params[0] != joo_node2js.JooNodeToJS.group_obj:
                    defs.err("WARNING: group object name is differ from " + 
                                    joo_node2js.JooNodeToJS.group_obj)

            #this = context.type == "IDENTIFIER" and "$" or (context[0].value + ".")
            this = "this"
            return "%s.Package(%r,function(%s)%s)" % (this, group_ns, group_obj, 
                                                        self.o(fun.body, i, c))
        
        else:
            return self.convert_function_call(n,i,dict(c))

    def convert_class(self, n, i, c):
        context = n[0]
        if ("unit" in c and len(context) == 2 and \
                context[0].value == c["unit"]["unit_obj"]) \
                or context.type == "IDENTIFIER":

            ns = ""
            className = ""
            ancestors = "null"
            classBody = "null"
            spec = "null"

            args = n[1]

            # class name
            if len(args) >= 1:
                assert args[0].type in ("STRING", "NULL")
                if args[0].type == "STRING":
                    className = args[0].value

            # ancestors
            if len(args) >= 2:
                assert args[1].type in ("ARRAY_INIT", "NULL")
                if args[1].type == "ARRAY_INIT" and len(args[1]) > 0:
                    ancestors = "[%s]" % ", ".join([ self.o(x,i,c) for x in args[1] ])

            class_obj = None

            c['class'] = {
                "spec": {},
                "name": className
            }

            # class body
            if len(args) >= 3:
                assert args[2].type in ("FUNCTION", "NULL")
                class_obj = ""
                if args[2].type == 'FUNCTION':
                    if len(args[2].params) > 0:
                        class_obj = c['class']["class_obj"] = args[2].params[0]
                        if c['class']["class_obj"] != joo_node2js.JooNodeToJS.class_obj:
                            defs.err("WARNING: class object name is differ from " + 
                                            joo_node2js.JooNodeToJS.class_obj)
                    classBody = "function(%s)%s" % (class_obj, self.o(args[2].body, i, c))

            # spec
            if len(args) == 4:
                spec = self.o(args[3], i, c)
            elif "spec" in c['class']:
                spec_ = c['class']["spec"]
                spec = []
                for prop, fields in spec_.copy().iteritems():
                    f = []
                    static = False
                    for k, v in fields.copy().iteritems():
                        if not v: continue
                        if k == "type":
                            f.append(["type", c["unit"]["unit_obj"] + "." + \
                                        "Type." + fields["type"].upper()])
                        elif k == "constructor":
                            f.append(["constructor", "true"])
                        elif k == "returnType":
                            f.append(["returnType", "Type." + fields["returnType"].upper()])
                        # props is public by default
                        elif k in ("internal", "hidden", "frozen", "sealed"):
                            f.append([k, "true"])
                        elif k == "static":
                            static = True

                    # FIXME skipp all fields for static prop
                    if not static:
                        spec.append([prop, f])

                if len(spec):
                    spec = "{%s}" % ",".join([ "%s:%s" % (x[0], 
                                "{%s}" % ",".join(["%s:%s" % (z[0], z[1]) for z in x[1]])) 
                                        for x in spec ])

            return "%sbuildClass(%s,%s,%s,%s)" % (
                        context.type == "IDENTIFIER" and "$" or (c["unit"]["unit_obj"] + "."),
                        className and repr(className), ancestors, classBody, spec)

        else:
            return self.convert_function_call(n,i,c)

    def conv_class_decl(self, n, i, c):
        context = n[0]
        args = n[1]
        if ("package" in c and len(context) == 2 and \
                context[0].value in (c["package"]["group_obj"], "this")) \
                or context.type == "IDENTIFIER":
            flags = args[0]
            ns = self.convert_namespace_flags(flags)
            _class = args[1]
            this = context.type == "IDENTIFIER" and "this" or (context[0].value)
            class_ = self.convert_class(_class,i,c)
            className = c["class"]["name"]
            assert className != "", "required class name"

            return "%s.%s = %s" % (this, className, class_)

        else:
            return self.convert_function_call(n,i,c)

    def conv_class_memb_decl(self, n, i_, c, override=False):
        context = n[0]
        args = n[1]
        if "class" in c and len(context) == 2 and \
                context[0].value in (c["class"]["class_obj"], "this"):
            collect = []
            spec = c["class"]["spec"]
            assert args[1].type in ("ARRAY_INIT", "STRING")
            if args[1].type == "ARRAY_INIT":
                if len(args) >= 3:
                    assert args[2].type in ("ARRAY_INIT", "NULL")
                if len(args) >= 4:
                    assert args[3].type in ("ARRAY_INIT", "NULL")
                if len(args) == 5:
                    assert args[4].type in ("ARRAY_INIT", "NULL")
                i = 0
                for x in args[1]:
                    collect.append([
                        x.value, 
                        len(args)>=3 and len(args[2])>0 and args[2][i] or None,
                        len(args)>=4 and len(args[3])>0 and args[3][i] or None,
                        len(args)>=5 and len(args[4])>0 and args[4][i] or None
                    ])
                    i += 1
                    
            else:
                if len(args)>=4: 
                    assert args[3].type == "IDENTIFIER" or args[3].type == "NULL" or \
                            (args[3].type == "DOT" and len(args[3]) == 2)
                if len(args)==5: 
                    assert args[4].type == "IDENTIFIER" or args[4].type == "NULL" or \
                            (args[4].type == "DOT" and len(args[4]) == 2)
                collect.append([
                    args[1].value, 
                    len(args)>=3 and args[2] or None,
                    len(args)>=4 and args[3] or None,
                    len(args)==5 and args[4] or None
                ])

            flags = args[0]
            # override is compiler directive
            ns = set() #set(override and ["override"] or [])
            flags_ = set(self.convert_namespace_flags(flags))
            ns = ns.union(flags_)
            memb = []
            for x in collect:
                name = x[0]
                expr = x[1]
                extype = x[1] and x[1].type or None
                #type_ = x[2] and self.o(x[2],i,c) or ""
                type_ = ""
                constructor = ""
                if x[2]:
                    if x[2].type == "NULL": pass
                    elif x[2].type == "IDENTIFIER":
                        type_ = x[2].value
                    elif x[2].type == "DOT" and \
                            x[2][0].value in ("unit" in c and c["unit"]["unit_obj"], "this"):
                        type_ = x[2][-1].value
                    else:
                        raise Exception, "Type error"

                retType = ""
                if x[3]:
                    if x[3].type == "NULL": pass
                    elif x[3].type == "IDENTIFIER":
                        retType = x[3].value
                    elif x[3].type == "DOT" and \
                            x[3][0].value in ("unit" in c and c["unit"]["unit_obj"], "this"):
                        retType = x[3][-1].value
                    else:
                        raise Exception, "Return type error"

                this = "this"
                if "static" in ns:
                    assert c["class"]["class_obj"] != "", "required class object"
                    this = c["class"]["class_obj"]

                if extype == "FUNCTION":
                    type_ = "Function"
                    c["function"] = {}
                    expr.functionForm = 1
                    if name == "__new__":
                        constructor = "true"
                    if len(expr.params) > 0 and not "static" in ns:
                        assert expr.params[0] == 'self'
                    memb.append("%s.%s = %s" % (this, name, self.o(expr,i_,c)))
                else:
                    memb.append("%s.%s%s" % (this, name,
                        expr and extype != "NULL" and " = %s" % self.o(expr,i_,c) or " = null"))

                spec[name] = dict([ [x, "true"] for x in ns])
                spec[name]["returnType"] = retType
                spec[name]["type"] = type_
                spec[name]["constructor"] = constructor

            return "%s" % (", ".join(memb))

        else:
            return self.convert_function_call(n,i_,c)

    def convert_import(self, n, i, c):
        context = n[0]
        args = n[1]
        if "unit" in c and len(context) == 2 and \
                context[0].value in (c["unit"]["unit_obj"], "this"):

            this = "this"
            return "%s.Import(%s)" % (this, ", ".join([repr(x.value) for x in args]))

        else:
            return self.convert_function_call(n,i,c)

    def convert_call(self, n, i, c):
        self.check(n,subnodes=2)

        if n[0].type == "IDENTIFIER":
            method = n[0].value[0] == "$" and n[0].value[1:] or n[0].value
        elif n[0].type == "DOT":
            method = n[0][-1].value
        else:
            method = None

        #print method

        if method == "Unit":
            return self.convert_unit(n,i,c)

        elif method == "Package":
            return self.convert_package(n,i,c)

        elif method == "addClass":
            return self.conv_class_decl(n,i,c)

        elif method == "buildClass":
            return self.convert_class(n,i,c)

        elif method in ("__add__", "__set__"):
            return self.conv_class_memb_decl(n,i,c,method=="__set__")

        elif method == "Import":
            return self.convert_import(n,i,c)

        else:
            return self.convert_function_call(n,i,c)

    def convert_function_call(self, n, i, c):
        return "%s(%s)" % (self.o(n[0], i, c), self.o(n[1], i, c))

    """
    def convert_and_or(self, n, i, c):
        self.check(n,subnodes=2)
        n.value = n.type == "AND" and "and" or "or"
        return "%s %s %s" % (self.o(n[0],i,c), n.value, self.o(n[1],i,c))

    def convert_not(self, n, i, c):
        self.check(n,subnodes=1)
        return "%s %s" % ("not", self.o(n[0],i,c))

    convert_and = \
    convert_or = convert_and_or
    """

def convert(parsetree, include_props=False):
    joo = NodeToJoo()
    return joo.convert(parsetree, "", {"include_props": include_props}) + "\n"
